{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1b236e0a",
   "metadata": {},
   "source": [
    "# Sweeps - Impedance, scattering and admittance (Z S Y) matrices"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ced70c0a",
   "metadata": {},
   "source": [
    "### Prerequisite\n",
    "You need to have a working local installation of Ansys"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b4c0b1a",
   "metadata": {},
   "source": [
    "## 1. Perform the necessary imports and create a QDesign in Metal first."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac34fec5",
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8a672f85",
   "metadata": {},
   "outputs": [],
   "source": [
    "import qiskit_metal as metal\n",
    "from qiskit_metal import designs, draw\n",
    "from qiskit_metal import MetalGUI, Dict, Headings\n",
    "import pyEPR as epr\n",
    "from qiskit_metal.analyses.simulation import ScatteringImpedanceSim"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e7b9b1f",
   "metadata": {},
   "source": [
    "## Create the design in Metal\n",
    " Set up a design of a given dimension.  Create a design by specifying the chip size and open Metal GUI.  Dimensions will be respected in the design rendering.  Note the chip design is centered at origin (0,0)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "645c9820",
   "metadata": {},
   "outputs": [],
   "source": [
    "design = designs.DesignPlanar({}, True)\n",
    "design.chips.main.size['size_x'] = '2mm'\n",
    "design.chips.main.size['size_y'] = '2mm'\n",
    "\n",
    "gui = MetalGUI(design)\n",
    "\n",
    "# Perform the necessary imports.\n",
    "from qiskit_metal.qlibrary.couplers.coupled_line_tee import CoupledLineTee\n",
    "from qiskit_metal.qlibrary.tlines.meandered import RouteMeander\n",
    "from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket\n",
    "from qiskit_metal.qlibrary.tlines.straight_path import RouteStraight\n",
    "from qiskit_metal.qlibrary.terminations.open_to_ground import OpenToGround"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f9558f23",
   "metadata": {},
   "outputs": [],
   "source": [
    "# To create plots after geting solution data.\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2387e31",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add 2 transmons to the design.\n",
    "options = dict(\n",
    "    # Some options we want to modify from the deafults\n",
    "    # (see below for defaults)\n",
    "    pad_width='425 um',\n",
    "    pocket_height='650um',\n",
    "    # Adding 4 connectors (see below for defaults)\n",
    "    connection_pads=dict(a=dict(loc_W=+1, loc_H=+1),\n",
    "                         b=dict(loc_W=-1, loc_H=+1, pad_height='30um'),\n",
    "                         c=dict(loc_W=+1, loc_H=-1, pad_width='200um'),\n",
    "                         d=dict(loc_W=-1, loc_H=-1, pad_height='50um')))\n",
    "\n",
    "## Create 2 transmons\n",
    "q1 = TransmonPocket(design,\n",
    "                    'Q1',\n",
    "                    options=dict(pos_x='+1.4mm',\n",
    "                                 pos_y='0mm',\n",
    "                                 orientation='90',\n",
    "                                 **options))\n",
    "q2 = TransmonPocket(design,\n",
    "                    'Q2',\n",
    "                    options=dict(pos_x='-0.6mm',\n",
    "                                 pos_y='0mm',\n",
    "                                 orientation='90',\n",
    "                                 **options))\n",
    "\n",
    "gui.rebuild()\n",
    "gui.autoscale()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6f4efad9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add 2 hangers consisting of capacitively coupled transmission lines.\n",
    "TQ1 = CoupledLineTee(design,\n",
    "                     'TQ1',\n",
    "                     options=dict(pos_x='1mm',\n",
    "                                  pos_y='3mm',\n",
    "                                  coupling_length='500um',\n",
    "                                  coupling_space='1um'))\n",
    "TQ2 = CoupledLineTee(design,\n",
    "                     'TQ2',\n",
    "                     options=dict(pos_x='-1mm',\n",
    "                                  pos_y='3mm',\n",
    "                                  coupling_length='500um',\n",
    "                                  coupling_space='1um'))\n",
    "\n",
    "gui.rebuild()\n",
    "gui.autoscale()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9291dc36",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add 2 meandered CPWs connecting the transmons to the hangers.\n",
    "ops = dict(fillet='90um')\n",
    "design.overwrite_enabled = True\n",
    "\n",
    "options1 = Dict(total_length='8mm',\n",
    "                hfss_wire_bonds=True,\n",
    "                pin_inputs=Dict(start_pin=Dict(component='TQ1',\n",
    "                                               pin='second_end'),\n",
    "                                end_pin=Dict(component='Q1', pin='a')),\n",
    "                lead=Dict(start_straight='0.1mm'),\n",
    "                **ops)\n",
    "\n",
    "options2 = Dict(total_length='9mm',\n",
    "                hfss_wire_bonds=True,\n",
    "                pin_inputs=Dict(start_pin=Dict(component='TQ2',\n",
    "                                               pin='second_end'),\n",
    "                                end_pin=Dict(component='Q2', pin='a')),\n",
    "                lead=Dict(start_straight='0.1mm'),\n",
    "                **ops)\n",
    "\n",
    "meanderQ1 = RouteMeander(design, 'meanderQ1', options=options1)\n",
    "meanderQ2 = RouteMeander(design, 'meanderQ2', options=options2)\n",
    "\n",
    "gui.rebuild()\n",
    "gui.autoscale() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ad7438f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add 2 open to grounds at the ends of the horizontal CPW.\n",
    "otg1 = OpenToGround(design, 'otg1', options=dict(pos_x='3mm', pos_y='3mm'))\n",
    "otg2 = OpenToGround(design,\n",
    "                    'otg2',\n",
    "                    options=dict(pos_x='-3mm', pos_y='3mm', orientation='180'))\n",
    "\n",
    "gui.rebuild()\n",
    "gui.autoscale()\n",
    "\n",
    "# Add 3 straight CPWs that comprise the long horizontal CPW.\n",
    "\n",
    "ops_oR = Dict(hfss_wire_bonds=True,\n",
    "              pin_inputs=Dict(start_pin=Dict(component='TQ1', pin='prime_end'),\n",
    "                              end_pin=Dict(component='otg1', pin='open')))\n",
    "ops_mid = Dict(hfss_wire_bonds=True,\n",
    "               pin_inputs=Dict(start_pin=Dict(component='TQ1',\n",
    "                                              pin='prime_start'),\n",
    "                               end_pin=Dict(component='TQ2', pin='prime_end')))\n",
    "ops_oL = Dict(hfss_wire_bonds=True,\n",
    "              pin_inputs=Dict(start_pin=Dict(component='TQ2',\n",
    "                                             pin='prime_start'),\n",
    "                              end_pin=Dict(component='otg2', pin='open')))\n",
    "\n",
    "cpw_openRight = RouteStraight(design, 'cpw_openRight', options=ops_oR)\n",
    "cpw_middle = RouteStraight(design, 'cpw_middle', options=ops_mid)\n",
    "cpw_openLeft = RouteStraight(design, 'cpw_openLeft', options=ops_oL)\n",
    "\n",
    "gui.rebuild()\n",
    "gui.autoscale()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bfca232",
   "metadata": {},
   "source": [
    "## 2. Render the qubit from Metal into the HangingResonators design in Ansys. <br>ScatteringImpedanceSim will  open the simulation software. Then will connect, activate the design, add a setup.  \n",
    "\n",
    "Review and update the setup. For driven modal you will need to define not only the simulation convergence parameters, but also the frequency sweep.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3f302b13",
   "metadata": {},
   "outputs": [],
   "source": [
    "em1 = ScatteringImpedanceSim(design, \"hfss\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f02f684-c628-4efb-a82e-129c1323d88e",
   "metadata": {},
   "source": [
    "Customizable parameters and default values for HFSS (driven  modal):\n",
    "\n",
    "    freq_ghz=5 (simulation frequency)\n",
    "    name=\"Setup\" (setup name)\n",
    "    max_delta_s=0.1 (absolute value of maximum difference in scattering parameter S)\n",
    "    max_passes=10 (maximum number of passes)\n",
    "    min_passes=1 (minimum number of passes)\n",
    "    min_converged=1 (minimum number of converged passes)\n",
    "    pct_refinement=30 (percent refinement)\n",
    "    basis_order=1 (basis order)\n",
    "    vars (global variables to set in the renderer)\n",
    "    sweep_setup (all the parameters of the sweep)\n",
    "    name=\"Sweep\" (name of sweep)\n",
    "    start_ghz=2.0 (starting frequency)\n",
    "    stop_ghz=8.0 (stopping frequency)\n",
    "    count=101 (total number of frequencies)\n",
    "    step_ghz=None (frequency step size)\n",
    "    type=\"Fast\" (type of sweep)\n",
    "    save_fields=False (whether or not to save fields)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "834584b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# To view the values for defaults. \n",
    "em1.setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de6fa174",
   "metadata": {},
   "outputs": [],
   "source": [
    "em1.setup.name = \"Sweep_DrivenModal_setup\"\n",
    "em1.setup.freq_ghz = 6.0\n",
    "em1.setup.max_delta_s = 0.05\n",
    "em1.setup.max_passes = 12\n",
    "em1.setup.min_passes = 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d54a538",
   "metadata": {},
   "source": [
    "\n",
    "Add a frequency sweep to a driven modal setup.<br>\n",
    "From QHFSSRenderer.add_sweep doc_strings.  Please go to doc_strings to get the latest information.  \n",
    "\n",
    "Args:<br>\n",
    "    setup_name (str, optional): Name of driven modal simulation Sweep.\n",
    "                            Defaults to \"Setup\".<br>\n",
    "    start_ghz (float, optional): Starting frequency of sweep in GHz.\n",
    "                            Defaults to 2.0.<br>\n",
    "    stop_ghz (float, optional): Ending frequency of sweep in GHz.\n",
    "                            Defaults to 8.0.<br>\n",
    "    count (int, optional): Total number of frequencies.\n",
    "                            Defaults to 101.<br>\n",
    "    step_ghz (float, optional): Difference between adjacent\n",
    "                            frequencies. Defaults to None.<br>\n",
    "    name (str, optional): Name of sweep. Defaults to \"Sweep\".<br>\n",
    "    type (str, optional): Type of sweep. Defaults to \"Fast\".<br>\n",
    "    save_fields (bool, optional): Whether or not to save fields.\n",
    "                        Defaults to False.<br>\n",
    "                        \n",
    "                        \n",
    "From pyEPR.HfssSetup.insert_sweep(), please go to this method to get the latest documentation.   \n",
    "You should provide either step_ghz or count when inserting an HFSS driven model freq sweep. Do not provide both or neither!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9f5f6acd-4809-4455-b2cf-de0fb0c83059",
   "metadata": {},
   "outputs": [],
   "source": [
    "# To view the values for defaults. \n",
    "em1.setup.sweep_setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9135df20",
   "metadata": {},
   "outputs": [],
   "source": [
    "em1.setup.sweep_setup.name=\"Sweep_options__dm_sweep\"\n",
    "em1.setup.sweep_setup.start_ghz=4.0\n",
    "em1.setup.sweep_setup.stop_ghz=9.0\n",
    "em1.setup.sweep_setup.count=5001\n",
    "em1.setup.sweep_setup.type=\"Interpolating\"\n",
    "\n",
    "\n",
    "em1.setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d00fa8c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set the buffer width at the edge of the design to be 0.5 mm \n",
    "# in both directions.\n",
    "em1.setup.renderer.options['x_buffer_width_mm'] = 0.5\n",
    "em1.setup.renderer.options['y_buffer_width_mm'] = 0.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ce9dbaa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "#     qcomp_name (str): A component that contains the option to be swept.\n",
    "#     option_name (str): The option within qcomp_name to sweep.\n",
    "#     option_sweep (list): Each entry in the list is a value for\n",
    "#                 option_name.\n",
    "#     qcomp_render (list): The component to render to simulation.\n",
    "#     open_terminations (list): Identify which kind of pins. Follow the\n",
    "#                 details from renderer QQ3DRenderer.render_design, or\n",
    "#                 QHFSSRenderer.render_design.\n",
    "#     port_list (list): List of tuples of jj's that shouldn't\n",
    "#                     be rendered.  Follow details from\n",
    "#                     renderer in QHFSSRenderer.render_design.\n",
    "#     jj_to_port (list): List of junctions (qcomp, qgeometry_name,\n",
    "#                         impedance, draw_ind) to render as lumped ports\n",
    "#                         or as lumped port in parallel with a sheet\n",
    "#                         inductance.    Follow details from renderer\n",
    "#                         in QHFSSRenderer.render_design.\n",
    "#     ignored_jjs (Union[list,None]): This is not used by all renderers,\n",
    "#                  just hfss.\n",
    "#     design_name(str): Name of design (workspace) to use in project.\n",
    "#     box_plus_buffer(bool): Render the entire chip or create a\n",
    "#                 box_plus_buffer around the components which are rendered.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "80500d37",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "design_name= \"Sweep_DrivenModal\"\n",
    "qcomp_render = [] # Means to render everything in qgeometry table.\n",
    "open_terminations = []\n",
    "\n",
    "# Here, pin cpw_openRight_end and cpw_openLeft_end are converted into lumped ports,\n",
    "#           each with an impedance of 50 Ohms. <br>\n",
    "port_list = [('cpw_openRight', 'end', 50),\n",
    "                      ('cpw_openLeft', 'end', 50)]\n",
    "jj_to_port = [('Q1', 'rect_jj', 50, False)]\n",
    "# Neither of the junctions in Q1 or Q2 are rendered.\n",
    "ignored_jjs = [('Q2', 'rect_jj')]\n",
    "box_plus_buffer = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "948aa9b4",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "#Note: The method will connect to  Ansys simulation, activate_drivenmodal_design(), add_drivenmodal_setup().\n",
    "\n",
    "all_sweeps, return_code = em1.run_sweep(meanderQ1.name,\n",
    "                                        'total_length', \n",
    "                                        ['9mm', '10mm', '11mm'],\n",
    "                                        qcomp_render,\n",
    "                                        open_terminations,\n",
    "                                        design_name=design_name,\n",
    "                                        port_list = port_list,\n",
    "                                        jj_to_port= jj_to_port,\n",
    "                                        ignored_jjs= ignored_jjs,\n",
    "                                        box_plus_buffer=box_plus_buffer\n",
    "                                       )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61ee7c0b-c41c-4fde-951b-87bdac17200c",
   "metadata": {
    "tags": []
   },
   "source": [
    "#Note: Sweep again using the arguments from previous run.  \n",
    "```\n",
    "all_sweeps_6_7_8, return_code = em1.run_sweep(meanderQ1.name,\n",
    "                                        'total_length', \n",
    "                                        ['6.5mm', '7.5mm', '8.5mm']\n",
    "                                       )\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c564b8d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "if return_code == 0:\n",
    "    # Each key corresponds to list passed to ['9mm', '8mm', '7mm']\n",
    "    print(all_sweeps.keys())\n",
    "    \n",
    "    # Each key corresponds to list passed to ['6mm', '5mm', '4mm']\n",
    "    # print(all_sweeps_6_7_8.keys())\n",
    "else:\n",
    "    print('Check warning messages to see why all_sweeps is non-zero.')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a53f4b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "all_sweeps['9mm'].keys()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "656a0690",
   "metadata": {},
   "outputs": [],
   "source": [
    "all_sweeps['9mm']['variables']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c5697567",
   "metadata": {},
   "outputs": [],
   "source": [
    "all_sweeps['9mm']['option_name']\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a8eb01c",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"\"\"\n",
    "project_name = {em1.renderer.pinfo.project_name}\n",
    "design_name  = {em1.renderer.pinfo.design_name}\n",
    "setup_name   = {em1.renderer.pinfo.setup_name}\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd2002a4",
   "metadata": {},
   "source": [
    "Note: Results storage is currently being updated to be fully functional with the sweep functionality."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ec8f294",
   "metadata": {},
   "outputs": [],
   "source": [
    "em1.get_impedance()                # default: ['Z11', 'Z21']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e40ac2a",
   "metadata": {},
   "outputs": [],
   "source": [
    "em1.get_admittance()              # default: ['Y11', 'Y21']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4640a8c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "em1.get_scattering(['S11', 'S21', 'S31'])          ## default: ['S11', 'S21', 'S22']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33a0735c",
   "metadata": {},
   "outputs": [],
   "source": [
    "dataframe_scattering = em1.get_scattering(['S11', 'S21', 'S31'])\n",
    "df_s = dataframe_scattering[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "929bb804",
   "metadata": {},
   "outputs": [],
   "source": [
    "s11 = df_s['S11']\n",
    "s11\n",
    "\n",
    "s21 = df_s['S21']\n",
    "s21\n",
    "\n",
    "s31 = df_s['S31']\n",
    "s31"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe5079dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "dataframe_scattering[0]['20_log_of_mag_S11']= 20 * np.log10(np.absolute(s11))\n",
    "dataframe_scattering[0]['20_log_of_mag_S21']= 20 * np.log10(np.absolute(s21))\n",
    "dataframe_scattering[0]['20_log_of_mag_S31']= 20 * np.log10(np.absolute(s31))\n",
    "dataframe_scattering[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e405022",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reference to current axis. \n",
    "magnitude = plt.figure('Magnitude S11, S21, and S31')\n",
    "plt.clf()\n",
    "axis = plt.gca() # Get current axis.\n",
    "dataframe_scattering[0].plot(kind = 'line', y='20_log_of_mag_S11', color = 'green', ax = axis)\n",
    "dataframe_scattering[0].plot(kind = 'line', y='20_log_of_mag_S21', color = 'blue', ax = axis)\n",
    "dataframe_scattering[0].plot(kind = 'line', y='20_log_of_mag_S31', color = 'red', ax = axis)\n",
    "plt.title(f'S-Parameter Magnitude')\n",
    "plt.xlabel(f'frequency [GHZ]')\n",
    "plt.ylabel(f'|S11|,|S21|,|S31| [dB]')\n",
    "magnitude.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dfd0b543",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Data is shown as degrees.  \n",
    "# However, if you want radians, change value of deg to false, deg=False.\n",
    "dataframe_scattering[0]['degrees_S11'] = np.angle(s11, deg=True)\n",
    "dataframe_scattering[0]['degrees_S21'] = np.angle(s21, deg=True)\n",
    "dataframe_scattering[0]['degrees_S31'] = np.angle(s31, deg=True)\n",
    "dataframe_scattering[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f8fc9895",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reference to current axis. \n",
    "phase = plt.figure('Phase of S11 and S21')\n",
    "plt.clf()\n",
    "axis = plt.gca() # Get current axis.\n",
    "dataframe_scattering[0].plot(kind = 'line', y='degrees_S11', color = 'green', ax = axis)\n",
    "dataframe_scattering[0].plot(kind = 'line', y='degrees_S21', color = 'blue', ax = axis)\n",
    "dataframe_scattering[0].plot(kind = 'line', y='degrees_S31', color = 'red', ax = axis)\n",
    "plt.title(f'S-Parameter Phase')\n",
    "plt.xlabel(f'frequency [GHZ]')\n",
    "plt.ylabel(f'<S11, <S21, <S31 [degrees]')\n",
    "phase.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7646293c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "em1.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33de9236",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Uncomment next line if you would like to close the gui\n",
    "# gui.main_window.close()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
